from urllib.request import urlopen
from urllib.request import Request
from urllib.error import URLError
from time import sleep
from bs4 import BeautifulSoup

# 是否打印调试信息
debug = False

# 下载网页
# @param url : 网页地址
# @param user_agent : 用户代理
# @param num_retries : 重试次数
def download(url, user_agent='NovelScraping', num_retries=2):
    if debug:
        print('Downloading: ' + url)
    headers = { 'User-agent': user_agent }
    request = Request(url, headers=headers)
    try:
        html = urlopen(request).read().decode("utf-8")
    except URLError as e:
        print('Download error: ' + e.reason)
        html = None
        if num_retries > 0:
            if hasattr(e, 'code') and 500 <= e.code < 600:
                sleep(5000)
                # recursively retry 5xx HTTP error
                return download(url, user_agent, num_retries - 1)
        
    return html

# 解析小说目录
def getNovelCatalogues(novel):
    html = download(novel['url'])
    if debug:
        print("html: ")
        print(html)
        print()
    if html != None:
        soup = BeautifulSoup(html, 'html.parser')
        list = soup.find(id='list')
        if debug: 
            print(str(list))
        if list != None:
            catalogues = {}
            for item in list.find_all('dd'):
                if debug:
                    print("item: " + str(item))
                a = item.find('a')
                if a != None and hasattr(a, 'href') and len(item.get_text()) > 0:
                    if debug:
                        print("name: " + item.get_text() + ", url: " + a['href'])
                    catalogues[item.get_text()] = novel['url'] + a['href'][a['href'].rindex('/') + 1:]
            if len(catalogues) > 0:
                return catalogues  
    return None

# 打印小说列表
def printNovelList(novels):
    print("小说列表：")
    print()
    index = 1
    for key in novels:
        print("%d. %s" % (index, key))
        index += 1

    print()

# 选择要阅读的小说
def selectNovel(novels):
    indexstr = input("请选择要阅读的小说编号：")
    while not indexstr.isdigit() or int(indexstr) > len(novels.keys()) or int(indexstr) <= 0:
        indexstr = input("你的输入不正确，请重新选择要阅读的小说编号：")

    if debug:
        print("index select: " + indexstr)

    selectIndex = int(indexstr)
    selectNovelName = None
    selectNovelAddress = None
    index = 1
    for key, value in novels.items():
        if index == selectIndex:
            selectNovelName = key
            selectNovelAddress = value
        index += 1

    if debug:
        print("Select novel name: " + selectNovelName + ", url: " + selectNovelAddress)
    return { 'name' : selectNovelName, 'url' : selectNovelAddress }

# 打印小说目录
def printNovelCatalogues(novel, novelCatalogues):
    print()
    print("《" + novel['name'] + "》 小说目录：")
    print()
    maxLen = 0
    for key in novelCatalogues:
        if len(key) > maxLen:
            maxLen = len(key)
    if debug:
        print("maxlen: " + str(maxLen))
    index = 1
    for key in novelCatalogues:
        if index % 3 == 0:
            width = maxLen * 2 - len(key) + getAsciiCount(key)
            item = "{:{}}".format(key, width)
            print(item)
        else:
            width = maxLen * 2 - len(key) + getAsciiCount(key)
            item = "{:{}}".format(key, width)
            print(item, end="")
        index += 1
    print()

# 选择要阅读的小说章节
def selectNovelChapter(novel, novelCatalogues):
    print()
    chapterSize = len(novelCatalogues)
    if debug:
        print("chapterSize: " + str(chapterSize))
    indexstr = input('请输入要阅读的小说章节编号（数字部分）(输入 q 返回上一层菜单）：')
    while (indexstr != None and indexstr.lower() != 'q') and (not str.isdigit(indexstr) or int(indexstr) > chapterSize or int(indexstr) <= 0):
        indexstr = input("你的输入不正确，请重新输入要阅读的小说章节编号（数字部分）(输入 q 返回上一层菜单）：")

    if debug:
        print("index select: " + indexstr)

    if indexstr.lower == 'q':
        return None
    
    selcetIndex = int(indexstr)
    selectChapterName = None
    selectChapterAddress = None
    index = 1
    for key, value in novelCatalogues.items():
        if selcetIndex == index:
            selectChapterName = key
            selectChapterAddress = value
        index += 1

    if debug:
        print("Select chapter name: " + selectChapterName + ", url: " + selectChapterAddress)

    return { 'name' : selectChapterName, 'url' : selectChapterAddress }

# 显示小说章节内容
def showNovelChapterContent(novel, chapter):
    html = download(chapter['url'])
    if debug:
        print("Chhapter content: ")
        print(html)
    if html == None:
        print()
        print("获取小说内容失败！")
        return
    soup = BeautifulSoup(html, 'html.parser')
    contentHtml = soup.find(id='content')
    if contentHtml == None:
        print()
        print("获取小说内容失败！")
        return
    print()
    content = str(contentHtml)
    content = content.replace('<div id="content">\n', '')
    content = content.replace('</div>', '')
    content = content.replace('<br/><br/>', '\n\n')
    print(content)
    

# 获取字符串中 ASCII 字符数量
def getAsciiCount(string):
    count = 0
    for char in string:
        if 0 <= ord(char) <= 127:
            count += 1
    return count

def __main__():
    novelwebaddress = {
        '寒门枭士' : 'https://www.bookabc.cc/abc/24/24228/',
        '我的二师兄吕少卿' : 'https://www.bookabc.cc/abc/94/94102/',
        '赝太子' : 'https://www.bookabc.cc/abc/17/17234/',
        '退出' : ''
    }
    # 打印小说菜单
    printNovelList(novelwebaddress)
    # 获取用户选择的小说网址
    novel = selectNovel(novelwebaddress)
    while novel['name'] != '退出':
        # 获取小说目录
        novelCatalogues = getNovelCatalogues(novel)
        if novelCatalogues == None:
            print()
            print("获取小说目录失败！")
            print()
            # 打印小说菜单
            printNovelList(novelwebaddress)
            # 获取用户选择的小说网址
            novel = selectNovel(novelwebaddress)
            continue
        
        # 打印小说目录
        printNovelCatalogues(novel, novelCatalogues)
        # 选择要阅读的小说章节
        chapter = selectNovelChapter(novel, novelCatalogues)
        while chapter != None and chapter['url'] != None:
            # 显示小说章节内容
            showNovelChapterContent(novel, chapter)
            confirme = input('是否继续阅读小说其他章节（Y/N): ')
            if debug:
                print("confirme: " + confirme + ", upper: " + confirme.upper())
            if confirme.upper() == 'Y':
                # 打印小说目录
                printNovelCatalogues(novel, novelCatalogues)
                # 选择要阅读的小说章节
                chapter = selectNovelChapter(novel, novelCatalogues)
            else:
                chapter = None

        print()
        # 打印小说菜单
        printNovelList(novelwebaddress)
        # 获取用户选择的小说网址
        novel = selectNovel(novelwebaddress)


# 运行程序
__main__()